#!/usr/bin/python

import threading
import time
from src import main_cl
import sys

ps1 = '\033[36m(bot sim) \033[00m'
config_dir = './configs/'
data_dir = './data/'

class command_line_opts:
  def __init__ (self):
    """
    Store command line options prior to program dispatch
    """
    #self.opts = 'abcdfgmnoprstuvw'
    self.opts = 'abdfgmnoprstuvwz'
    # Default values
    # Number of angles
    self.a = '4'
    # Brain
    self.brains = ['ran', 'it', 'mc', 'ql', 'qlb']
    self.b = 'qlb'
    # dt
    self.d = '0.4'
    # if file output, default file
    self.f = 'default'
    self.f_out = data_dir + self.f + '.' + self.b
    # Graphics
    self.graphics = ['on', 'off']
    self.g = 'off'
    # how many trials to average (mean) over
    self.m = '25'
    # number
    self.n = '1'
    # Output
    self.outputs = ['file', 'graph-dist', 'graph-coll', 'none']
    self.o = 'file'
    # Bot policy sharing
    self.p_sharing = ['full', 'none']
    self.p = 'full'
    # Bot radius
    self.r = '10'
    # Arena size
    self.s = '200x200'
    # Run duration; 0 is infinite
    self.t = '4000'
    # share temp
    self.u = '0.0'
    # Velocity limits
    self.v = '8.x8.'
    # omega_limits
    self.w = '-0.6x0.6'
    self.pacman = ['on', 'off']
    self.z = 'off'

  def parse (self, opt, val):
    
    invalid = 'Invalid option. See help (h)'
    if opt not in self.opts:
      print invalid
    elif opt == 'a':
      self.set_a (val)
    elif opt == 'b':
      self.set_b (val)
    elif opt == 'd':
      self.set_d (val)
    elif opt == 'e':
      self.set_e (val)
    elif opt == 'f':
      self.set_f (val)
    elif opt == 'g':
      self.set_g (val)
    elif opt == 'm':
      self.set_m (val)
    elif opt == 'n':
      self.set_n (val)
    elif opt == 'o':
      self.set_o (val)
    elif opt == 'p':
      self.set_p (val)
    elif opt == 'r':
      self.set_r (val)
    elif opt == 's':
      self.set_s (val)
    elif opt == 't':
      self.set_t (val)
    elif opt == 'u':
      self.set_u (val)
    elif opt == 'v':
      self.set_v (val)
    elif opt == 'w':
      self.set_w (val)
    elif opt == 'z':
      self.set_z (val)
    else:
      print invalid

  def save (self, filename='default_session'):
    """
    Save the current settings to a specified config file
    """
    f = open (config_dir+filename, 'w')
    for opt in self.opts:
      exec 'val = self.%s' % opt
      f.write (opt+' '+val+'\n')

    f.close ()

  def load (self, cl, name):
    try:
      f = open(config_dir+name)
      for line in f.readlines ():
        line = line.split ()
        if line:
          if line[0] in cl.opts:
            if len(line) != 2:
              print 'Are you trying to change the '+line[0]+' value through a config file?'
              print 'If so, you need one option and one value in the file, e.g. a 4'
            else:
              cl.parse (line[0], line[1])
          elif ' '.join(line)[0] == '#':
            pass
          else:
            print 'Unrecognized options in config file:', ' '.join(line)
    except IOError:
      print 'Error opening filename ', name
    return cl

  def check_density (self):
    max_den = 1./2.
    walls_str = self.s.split ('x')
    right_wall = float(walls_str[0])
    top_wall = float(walls_str[0])
    if 4.*int(self.n)*float(self.r)**2 > max_den*right_wall*top_wall:
      print 'Can\'t commit change. Bot density too high.'
      return False
    else:
      return True

  def set_a (self, a_val):
    try:
      if int (a_val) < 0:
        print 'Must have positive number for a.'
      else:
        self.a = a_val
    except ValueError:
      print 'Invalid entry for a.'

  def set_b (self, b_val):
    if b_val in self.brains:
      old_b = self.b
      self.b = b_val
      self.f_out = self.f_out.rstrip(old_b) + self.b
    else:
      print 'Invalid b selection. Must be one of the following:'
      for temp_b in self.brains:
        print '\t',temp_b

  def set_c (self, c_val):
    self.c = c_val

  def set_d (self, d_val):
    try:
      if float(d_val) <= 0.0:
        print 'Invalid time step d, must be > 0'
      else:
        self.d = d_val
    except ValueError:
      print 'invalid entry for d.'

  def set_f (self, f_val):
#    if self.o != 'file':
#      print 'Setting filename when output isn\'t to file...'
#      print 'Reminder: current output type is '+self.o
    self.f = f_val
    self.f_out = data_dir + self.f + '.' + self.b

  def set_g (self, g_val):
    if g_val in self.graphics:
      self.g = g_val
    else:
      print 'Invalid g option; must be \'on\' or \'off\'.'

  def set_m (self, m_val):
    try:
      if int (m_val) <= 0:
        print 'Must average over at least one value...'
      else:
        if self.d == '0' and int (m_val) > 1:
          print 'Note: m does not matter when program runs forever (d=0)'
        self.m = m_val
    except ValueError:
      print 'Invalid entry for m.'

  def set_n (self, n_val):
    try:
      n_temp = self.n
      self.n = n_val
      if int(n_val) <= 0:
        print 'Must have positive number for n.'
        self.n = n_temp
      elif self.check_density ():
        pass
      else:
        self.n = n_temp
    except ValueError:
      print 'Invalid entry for n.'

  def set_o (self, o_val):
    if o_val in self.outputs:
      self.o = o_val
    else:
      print 'Invalid o selection. Must be one of the following:'
      for temp_o in self.outputs:
        print '\t', temp_o

  def set_p (self, p_val):
    if p_val in self.p_sharing:
      self.p = p_val
    else:
      print 'Invalid o selection. Must be one of the following:'
      for temp_p in self.p_sharing:
        print '\t', temp_p

  def set_r (self, r_val):
    try:
      r_temp = self.r
      self.r = r_val
      if int(r_val) < 0:
        print 'invalid r option, must be positive.'
        self.r = r_temp
      elif self.check_density ():
        pass
      else:
        self.r = r_temp
    except ValueError:
      print 'Invalid entry for r.'

  def set_s (self, s_val):
    try:
      lims = s_val.split('x')
      s_temp = self.s
      self.s = s_val
      if len (lims) != 2:
        print 'Must specify exactly 2 dimensions for walls: e.g. \'100x100\'.'
        self.s = s_temp
      elif int(lims[0]) < 0 or int(lims[1]) < 0:
        print 'Must have positive values for wall limits.'
        self.s = s_temp
      elif self.check_density ():
        pass
      else:
        self.s = s_temp
    except ValueError:
      print 'Invalid entry for s.'

  def set_t (self, t_val):
    try:
      if int(t_val) < 0:
        print 'Invalid run duration t: must be 0 (infinite) or positive.'
      else:
        self.t = t_val
    except ValueError:
      print 'Invalid entry for t.'

  def set_u (self, u_val):
    try:
      if float (u_val) < 0. or float (u_val) > 1.:
        print 'Invalid sharing temperature. Must be 0 < u < 1.'
      else:
        self.u = u_val
    except ValueError:
      print 'Invalid entry for u. Must be a float.'

  def set_v (self, v_val):
    try:
      v_temp = v_val.split ('x')
      v_min = float (v_temp[0])
      v_max = float (v_temp[1])
      if len (v_temp) != 2:
        print 'Must specify exactly 2 velocity limits: e.g. \'5.x8.\'.'
      elif v_min > v_max:
        print 'Second v term must be greater than the first: e.g. \'5.x8.\'.'
      elif v_max < 0:
        print 'v terms must be positive: e.g. \'5.x8.\'.'
      else:
        self.v = v_val
    except ValueError:
      print 'Invalid entry for v.'

  def set_w (self, w_val):
    #print 'Setting omega needs implementing.'
    pass

  def set_z (self, z_val):
    if z_val in self.pacman:
      self.z = z_val
    else:
      print 'Invalid z option; must be \'on\' or \'off\'.'

help =\
'\
General commands:\n\
  exit\tQuit the program\n\
  help\tDisplay this help output\n\
  load\tTakes a path argument. Load a configuration file.\n\
  quit\tQuit the program\n\
  run \tDispatch the program with the current parameters.\n\
  save\tSave current configu file. Requires output name.\n\
  show\tShow a variable. \'show all\' shows all variables.\n\
Parameter modification commands: select an option and a value:\n\
Example: t 100 sets the run time per trial to 100.\n\
  a\tSelect number of angles the bot has (a>0)\n\
  b\tSelect the brain: (mc for monte-carlo, it for iterative, ran for random)\n\
  d\tSelect dt time step\n\
  f\tSelect output file. Extension is brain type (i.e. file.mc)\n\
  g\tTurn graphics (the animation) on or off\n\
  h\tDisplay this help output\n\
  m\tSelect number of trials to find the mean with\n\
  n\tSelect number of bots (n>0)\n\
  o\tSelect data output type (e.g. file)\n\
  p\tSelect policy sharing protocol (e.g. full for full policy)\n\
  q\tQuit the program; \'exit\' and \'quit\' also do this\n\
  r\tSelect bot radius (r>0)\n\
  s\tSet arena size (e.g. 200x200)\n\
  t\tSelect run duration; 0 is infinite\n\
  u\tSelect the sharing temperature -- or probability of accepting change\n\
  v\tSet v_min and v_max limits: v_min x v_max (e.g. 5.x8.)\n\
  w\tSet rotation rate limits (similar to v, not implemented)\n\
  z\tSecret pacman mode -- graphics on for this one!\
'

def show (cl, vals):
  for val in vals:
    if val == 'all':
      for v in cl.opts:
        print v+' = '+str(eval('cl.'+v))
    elif val in cl.opts:
      print val+' = '+str(eval('cl.'+val))
    else:
      print 'You requested to show an invalid value' + val + '.'

def set_main_parameters (cl, m):
  m.n_angles = int(cl.a)
  m.n_actions =  int(cl.a) # Don't forget this
  # Set the brain
  if cl.b == 'it':
    m.sel_brain = m.it_brain
  elif cl.b == 'mc':
    m.sel_brain = m.mc_brain
  elif cl.b == 'ql':
    m.sel_brain = m.ql_brain
  elif cl.b == 'qlb':
    m.sel_brain = m.ql_bolt_brain
  elif cl.b == 'ran':
    m.sel_brain = m.ran_brain
  m.dt = float(cl.d)
  if cl.g == 'on':
    m.graphics = True
  else:
    m.graphics = False
  if cl.z == 'on':
    m.pacman = True
  else:
    m.pacman = False

  m.output_filename = cl.f_out
  m.n_av = int(cl.m)
  m.num_bots = int(cl.n)
  m.output = cl.o
  
  m.sharing = cl.p
  m.share_temp = float (cl.u)

  m.bot_radius = int(cl.r)
  # Must be updated (quad tree depth, search radius, etc)
  m.update_from_rad (m.bot_radius)
  # The walls
  m.right_wall = int(cl.s.split('x')[0])
  m.top_wall = int(cl.s.split('x')[1])
  m.run_time = int(cl.t)
  m.v_min = float(cl.v.split('x')[0])
  m.v_max = float(cl.v.split('x')[1])
  m.omega_min = float(cl.w.split('x')[0])
  m.omega_max = float(cl.w.split('x')[1])

def run_main (cl, m):
  set_main_parameters (cl, m)
  print 'Main thread running.'
  m.run ()
  print '\nMain thread finished.'

class cl_loop (threading.Thread):
  
  def __init__ (self, cl, m):
    threading.Thread.__init__(self)
    self.cl = cl
    self.m = m
    self.finished = threading.Event ()
    self.loop (self.cl, self.m)

  def stop (self):
    self.finished.set ()
    self.join ()

  def loop (self, cl, m):
    s = raw_input (ps1)
    s = s.split ()
    
    if not s:
      return self.loop (cl, m)

    elif s[0] == 'h' or s[0] == 'help':
      print help
      return self.loop (cl, m)

    elif s[0] == 'load':
      if len(s) != 2:
        print 'Are you trying to load a file?'
        print 'If so, you need the option and input name: e.g. load filename'
      else:
        cl = cl.load (cl, s[1])
      return self.loop (cl, m)

    elif s[0] == 'save':
      if len(s) != 2:
        print 'Are you trying to save your current parameters to a config?'
        print 'If so, you need the option and the output name: e.g. save filename'
      else:
        cl.save (s[1])
      return self.loop (cl, m)

    elif s[0] == 'run':

      run_main (cl, m)
      
#      if cl.o == 'file':
#        # add the trial to a log
#        log = open(data_dir+'log', 'a')
#        line =\
#        'f='+cl.f+' : '+\
#        'b='+cl.b+' : '+\
#        'm='+cl.m+' : '+\
#        'n='+cl.n+' : '+\
#        's='+cl.s+' : '+\
#        'p='+cl.p+' : '+\
#        'u='+cl.u+'\n'
#        log.write (line)
#        log.close ()
#
      return self.loop (cl, m)

    elif s[0] == 'stop':
      t.stop ()
      return self.loop (cl, m)

    elif s[0] == 'show':
      if len(s) < 2:
        print 'Not showing any values... it was never specified.'
      else:
        vals = []
        for i in xrange (1, len(s)):
          vals += [s[i]]
        show (cl, vals)
      return self.loop (cl, m)

    elif s[0] in cl.opts:
      if len(s) != 2:
        print 'Are you trying to change the '+s[0]+' value?'
        print 'If so, you need one option and one value: e.g. a 4'
      else:
        cl.parse (s[0], s[1])
      return self.loop (cl, m)
    
    elif s[0] == 'exit' or s[0] == 'q' or s[0] == 'quit':
      return

    else:
      print 'Invalid option, see help (h).'
      return self.loop (cl, m)

def single_run (cl, m, config):
  cl = cl.load (cl, config)
  run_main (cl, m)

if __name__ == '__main__':
  cl = command_line_opts ()
  if len (sys.argv) == 1:
    cl_loop (cl, main_cl)
  else:
    single_run (cl, main_cl, sys.argv[1])

